5

很抱歉,这周实在是太忙了。。。不废话,直接进入主题。

引言

所谓的分析思路,无非就是找到程序的入口点,按执行流程一步一步地阅读分析,或者从自己感兴趣的部分入手。这个系列暂时还不是对Lua的全面剖析,只是选取其中一部分来分析。因此我们要找到自己感兴趣的部分,有针对性地进行分析。

由于Lua的官方文档比较齐全,浏览一下官方提供的资料集会比较便于我们着手分析。

所谓的源码分析,就是从源代码构建可执行程序,调试调试,看看执行流程,看看主要的数据结构和算法、程序的运行状态之类的,最后再品味一下设计(问题背景、原因、优缺点),仅此而已。

那我们先来看看源代码的布局。

代码布局

可以从Makefile文件入手,不过这里用的是Windows + Visual Studio,可以省去这个步骤。
看看从官方下载的源码包,提供了以下文件:

./lua-5.3.0/
    Makefile    README
    /doc/
        contents.html      logo.gif      lua.1
        lua.css            luac.1        manual.css
        manual.html        osi-certified-72x60.png
        readme.html
    /src/
        lapi.c       lapi.h       lauxlib.c    lauxlib.h
        lbaselib.c   lbitlib.c    lcode.c      lcode.h
        lcorolib.c   lctype.c     lctype.h     ldblib.c
        ldebug.c     ldebug.h     ldo.c        ldo.h
        ldump.c      lfunc.c      lfunc.h      lgc.c
        lgc.h        linit.c      liolib.c     llex.c
        llex.h       llimits.h    lmathlib.c   lmem.c
        lmem.h       loadlib.c    lobject.c    lobject.h
        lopcodes.c   lopcodes.h   loslib.c     lparser.c
        lparser.h    lprefix.h    lstate.c     lstate.h
        lstring.c    lstring.h    lstrlib.c    ltable.c
        ltable.h     ltablib.c    ltm.c        ltm.h
        lua.c        lua.h        lua.hpp      luac.c
        luaconf.h    lualib.h     lundump.c    lundump.h
        lutf8lib.c   lvm.c        lvm.h        lzio.c
        lzio.h       Makefile

第一件事当然是看README啦还用说,当然专业的做法可以看Makefile,这里为了避免引入其他无关知识,还是选用简单的方法。

按照说明查看doc/readme.html文件其中的【Building Lua on other systems】节,我们发现它介绍了lua可执行程序大致的组成和依赖关系如下:

library:
        lapi.c          lcode.c         lctype.c        ldebug.c
        ldo.c           ldump.c         lfunc.c         lgc.c
        llex.c          lmem.c          lobject.c       lopcodes.c
        lparser.c       lstate.c        lstring.c       ltable.c
        ltm.c           lundump.c       lvm.c           lzio.c
        lauxlib.c       lbaselib.c      lbitlib.c       lcorolib.c
        ldblib.c        liolib.c        lmathlib.c      loslib.c
        lstrlib.c       ltablib.c       lutf8lib.c      loadlib.c
        linit.c
interpreter:
        library,        lua.c
compiler:
        library,        luac.c

根据这个简单的依赖关系,我用VS2015建立了相应的工程,便于调试。其实官方资料集里也提供了现成的VS工程的下载链接:

TODO:此处应有下载链接^_^

因为我们分析的重点是编译原理和虚拟机的部分,而不是相关的库的实现部分。因此应该从 lua.c 或 luac.c 开始入手,其实我们从文件列表中也可以看出,里面有几个比较重要的文件:

llex.c    lopcodes.c    lparser.c    lvm.c

到底从哪里入手比较好,就见仁见智了。这里我还是采用了官方提供的资料来帮助选择。
官方的资料集的wiki中提供了一个页面。该页面介绍了这些文件的用途、编程约定、模块结构等等。
为了避免这个页面失效,下面还是可耻地复制粘贴略带翻译地提供给大家,网上也有一些翻译,只是他们翻译时省略掉了一些我觉得有用的信息。个别简单的,我就不翻译了。

Lua源码的模块结构

实用功能模块

  • ldebug.c - 调试接口。包括以下功能:

    1. 访问调试钩子 (lua_sethook, lua_gethook, lua_gethookcount),

    2. 访问运行时栈信息 (lua_getstack / lua_getlocal / lua_setlocal),

    3. 检查字节码 (luaG_checkopenop / luaG_checkcode),

    4. 引发错误 (luaG_typeerror / luaG_concaterror / luaG_aritherror /luaG_ordererror / luaG_errormsg / luaG_runerror)

  • lzio.c - 一种缓冲输入流接口。

  • lmem.c - 内存管理接口。它实现了这些内存分配函数:luaM_realloc / luaM_growaux_

  • lgc.c - 增量式GC (内存管理)

基本数据类型的实现模块

  • lstate.c - 全局状态。包括:

    • 用于打开和关闭Lua states的函数(lua_newstate/lua_close)

    • 线程相关函数 (luaE_newthread / luaE_freethread)

  • lobject.c - 一些针对Lua对象的通用函数。包括:

    • 数据类型与其字符串形式的互相转换

    • 原始数据相等性测试(luaO_rawequalObj)

    • 日志基础设施2(luaO_log2)

  • lstring.c - string table (持有由Lua处理的所有字符串)

  • lfunc.c - 用来操纵原型和闭包的辅助函数。

  • ltable.c - Lua tables (hash)

语法分析与代码生成相关模块

  • lcode.c - Lua的代码生成器. 由 lparser.c 来使用

  • llex.c - 词法分析器. 由 lparser.c 来使用

  • lparser.c - Lua 解析器.

  • lundump.c - 加载经过预编译的Lua代码块:

    • 实现了用于加载预编译后的代码块的luaU_undump 函数。

    • 还提供了用来解析函数头的 luaU_header 函数(在luaU_undump()内部被调用)。

  • ldump.c - 用于保存经过预编译的Lua代码块:

    • 实现了用于转储函数对象的luaU_dump()函数。这种函数对象是从文件或字符串预编译而来的Lua代码块。

处理执行Lua字节码的模块

  • lopcodes.c - 由Lua虚拟机使用的操作码。

    • 通过 luaP_opnames 和 luaP_opmodes这两个映射表,定义了所有操作码的名称和相关信息。

  • lvm.c - Lua虚拟机:

    • 用于执行字节码 (luaV_execute).

    • 还暴露了少量函数供 lapi.c 使用(如:luaV_concat).

  • ldo.c - Lua函数调用和栈管理。处理函数调用 (luaD_call / luaD_pcall), 栈生长, 协程处理等

  • ltm.c - 标签方法(tag methods)。 实现了查询对象中的元方法的功能。

标准库的实现模块

lbaselib.c - (base functions)
lstrlib.c  - string
ltablib.c  - table
lmathlib.c - math
loslib.c   - os
liolib.c   - io
loadlib.c  - package
ldblib.c   - debug

以下模块定义了 C API

  • lapi.c - Lua API. Lua C API的主要实现部分(lua_* functions).

  • lauxlib.c - 定义了 luaL_* 函数

  • linit.c - 实现了 luaL_openlibs 用于从C语言环境中加载上述模块。

lua 和 luac 程序的实现模块

  • lua.c - 独立的Lua 解析器

  • print.c - 定义了 "PrintFunction?" 函数,这些函数用于打印字节码 (通过luac.c "-l" 选项来使用)

  • luac.c - Lua 编译器 (保存字节码到文件/列出字节码)

感兴趣的模块

搞清楚Lua的模块结构后,我们需要着重分析的模块就出来了:

  • lua 和 luac 程序的实现模块

  • 处理执行Lua字节码的模块

  • 语法分析与代码生成相关模块

  • 基本数据类型的实现模块

当然,到了后期,如果有时间,我还会再分析一下Lua的垃圾收集器,但最近实在是太忙太忙了。

从Makefile来看模块间的依赖关系

以下是对 lua-5.3.0/src/Makefile 文件重要部分的节选

LUA_A=    liblua.a
CORE_O=    lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o \
    lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o \
    ltm.o lundump.o lvm.o lzio.o
LIB_O=    lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o  \
    lmathlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o loadlib.o linit.o
BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS)

LUA_T=    lua
LUA_O=    lua.o

LUAC_T=    luac
LUAC_O=    luac.o

根据我们感兴趣的模块,大概是以下目标文件:

  1. lua.o

  2. luac.o

  3. llex.o

  4. ldo.o

  5. lstate.o

  6. lparser.o

  7. lopcodes.o

  8. lvm.o

  9. lcode.o

  10. ldump.o

  11. lundump.o

  12. ltm.o

  13. lobject.o

  14. lstring.o

  15. ltable.o

  16. lfunc.o

(不得不吐槽这Markdown编辑器的Bug,莫名其妙给其他其他条目自动加粗了。。。我原本都不是这么写的!!!!)
其实还是挺多的,不过比较重要的都用粗体标出来了。大致就是按这个顺序去分析。这些其实也可以直接看源文件中引用的头文件,但那样太麻烦了,并且,Lua源码有时候并不是在文件的顶部写include指令,而是在中间的某个地方,比较蛋疼。看Makefile是比较方便而且专业的做法。我们继续看一下Makefile 文件中上述目标文件依赖关系:

lua.o: lua.c lprefix.h lua.h luaconf.h lauxlib.h lualib.h

luac.o: luac.c lprefix.h lua.h luaconf.h lauxlib.h lobject.h llimits.h \
  lstate.h ltm.h lzio.h lmem.h lundump.h ldebug.h lopcodes.h

llex.o: llex.c lprefix.h lua.h luaconf.h lctype.h llimits.h ldo.h \
  lobject.h lstate.h ltm.h lzio.h lmem.h lgc.h llex.h lparser.h lstring.h \
  ltable.h

ldo.o: ldo.c lprefix.h lua.h luaconf.h lapi.h llimits.h lstate.h \
  lobject.h ltm.h lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h lopcodes.h \
  lparser.h lstring.h ltable.h lundump.h lvm.h

lstate.o: lstate.c lprefix.h lua.h luaconf.h lapi.h llimits.h lstate.h \
  lobject.h ltm.h lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h llex.h \
  lstring.h ltable.h

lparser.o: lparser.c lprefix.h lua.h luaconf.h lcode.h llex.h lobject.h \
  llimits.h lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h \
  ldo.h lfunc.h lstring.h lgc.h ltable.h

lopcodes.o: lopcodes.c lprefix.h lopcodes.h llimits.h lua.h luaconf.h

lvm.o: lvm.c lprefix.h lua.h luaconf.h ldebug.h lstate.h lobject.h \
  llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lstring.h \
  ltable.h lvm.h

lcode.o: lcode.c lprefix.h lua.h luaconf.h lcode.h llex.h lobject.h \
  llimits.h lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h \
  ldo.h lgc.h lstring.h ltable.h lvm.h

这里简要地说一下Makefile语法:冒号左边的.o是编译时生成的目标文件,冒号右边是生成这个目标文件需要的源文件,\ 表示还没写完,下一行继续写。上一段Makefile代码中,等号左边是一个变量,等号右边是这个变量的值。

关于Makefile:Makefile是GNU/Linux系统中用于自动化构建的DSL,供gnu make使用,和Android开发中的.gradle文件作用类似。不过VS和Qt也都使用了各自的Makefile格式和工具。VS有nmake,Qt有qmake,跨平台的有cmake,这里就不展开说了,自己去看文档吧。

现在,我们可以比较有针对性地去看源文件了。

关于下一期

下一期将介绍一下Lua源码中的编程约定(其实还是做个搬运工+翻译工,这里的翻译就凑合着看吧,翻译质量应该没有太大问题)。前面这几期都是比较无聊又不可或缺的,只能忍忍啦。预报一下,后天将更新第三弹。

参考资料

  1. 模块结构的简介 http://lua-users.org/wiki/LuaSource


一方通行
178 声望12 粉丝